package com.google.code.pomhelper;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Print all declared modules including submodules.
 * 
 * @author rm
 * 
 */

@Mojo(name = "print-modules")
public class PrintModulesMojo extends AbstractMojo {

	/**
	 * The Maven project to analyze.
	 */
	@Component
	private MavenProject project;

	/**
	 * Skip plugin execution completely.
	 */
	@Parameter(property = "print-modules.skip", defaultValue = "false")
	private boolean skip;

	/**
	 * Sort modules alphabetical.
	 */
	@Parameter(property = "print-modules.sort", defaultValue = "false")
	private boolean sort;

	private int depth = 0;

	@Override
	public void execute() throws MojoExecutionException, MojoFailureException {
		if (isSkip()) {
			getLog().info("Skipping plugin execution");
			return;
		}

		@SuppressWarnings("unchecked")
		List<String> modules = project.getModules();

		if (getLog().isInfoEnabled() && modules != null && modules.size() > 0) {
			Modul modul = new Modul(0);
			modul.setModel(project.getModel());

			try {

				List<Modul> moduleList = new ArrayList<Modul>();

				getModules(modul, moduleList);

				if (sort) {
					Collections.sort(moduleList, new Modul.ModuleListComparator());
					for (Modul mod : moduleList) {
						getLog().info(mod.getName());
					}
				} else {
					printModules(moduleList);
				}
			} catch (IOException e) {
				getLog().error(e.getMessage());
			} catch (XmlPullParserException e) {
				getLog().error(e.getMessage());
			}
		}
	}

	private void printModules(List<Modul> moduleList) {
		for (Modul mod : moduleList) {
			StringBuilder indent = new StringBuilder("");
			for (int i = 0; i < mod.getDepth(); i++) {
				indent.append("  ");
			}

			getLog().info(indent.toString() + mod.getName());
		}
	}

	/**
	 * Gets the moduls from current pom and child-poms.
	 * 
	 * @param modul
	 * @param moduleList
	 * @throws IOException
	 * @throws XmlPullParserException
	 */
	private void getModules(Modul modul, List<Modul> moduleList) throws IOException, XmlPullParserException {
		for (String mod : modul.getModel().getModules()) {
			Modul newModul = new Modul(depth);
			moduleList.add(newModul);

			String pomPath = StringUtils.isEmpty(modul.getPomPath()) ? "" : modul.getPomPath() + File.separator;

			setModelAndPom(mod, newModul, pomPath);

			if (newModul.getModel().getModules().size() > 0) {
				depth++;
				getModules(newModul, moduleList);
				depth--;
			}
		}
	}

	/**
	 * set {@link Model} and pomPath in {@link Modul}.
	 * 
	 * @param mod
	 * @param modul
	 * @param pomPath
	 * @throws IOException
	 * @throws XmlPullParserException
	 */
	private void setModelAndPom(String mod, Modul modul, String pomPath) throws IOException, XmlPullParserException {
		MavenXpp3Reader pomReader = new MavenXpp3Reader();
		Model model = null;
		Reader reader = null;
		try {
			File file = new File(pomPath + mod + File.separator + "pom.xml");

			modul.setPomPath(file.getCanonicalFile().getParent());

			reader = ReaderFactory.newXmlReader(file);
			model = pomReader.read(reader);
			modul.setModel(model);
		} finally {
			IOUtil.close(reader);
		}
	}

	/**
	 * @return the skip
	 */
	public boolean isSkip() {
		return skip;
	}

	/**
	 * @param skip
	 *            the skip to set
	 */
	public void setSkip(boolean skip) {
		this.skip = skip;
	}

	/**
	 * @return the sort
	 */
	public boolean isSort() {
		return sort;
	}

	/**
	 * @param sort
	 *            the sort to set
	 */
	public void setSort(boolean sort) {
		this.sort = sort;
	}
}
